The following is a technique that can convert ERB files (Embeded Ruby) to
executable files like any other ruby/python/shell scripts, the difference is
that ERB is more suitable in cases where you have some output that needs to be
filled with data, like rails view files, or a text that needs to report number of
files/dependencies, in this post I will try to explore this idea.
first I created a file with this content
Today is : <%= Time.new %>
You can render the file with erb
as it’s command line tool to render erb
files and output the result to the terminal.
1erb date.erb
will output something similar to the following
Today is : 2018-01-24 21:08:20 +0100
As Linux looks for shebang in
every executable file, then if this erb file given the executable permission and
prepended by a shebang for erb it will be executed nearly the same way but
without explicitly specifying erb in the terminal, so first rename the file
1mv date.erb dateprinter
and give it the executable permission
1chmod +x dateprinter
now prepend it with a shebang for erb, it should look like this now
#!/usr/bin/env erb
Today is : <%= Time.new %>
now it could be executed as follows
1./dateprinter
the only difference is that the output will have the shebang line also, as ERB
doesn’t parse it and consider it part of the template file.
1#!/usr/bin/env erb
2Today is : 2018-01-24 21:08:20 +0100
if you put the previous file in a directory that is added to your shell $PATH
you can execute it from anywhere by it’s name like any other executable file.
1dateprinter
So in that case your shell will look for executable file in the path, will find
the dateprinter file, it’ll try to figure out how to execute it with exec
so
it’ll inspect for magic bytes, it’ll find the shebang, so it’ll pass the file
to the appropriate interpreter erb
and erb in that case will execute it and
hand you the output.
now lets do something useful with this idea, lets create a template that print
the number of dependencies for a rails application.
When you execute bundle install
the last 2 lines looks like this
1Bundle complete! 27 Gemfile dependencies, 109 gems now installed.
2Use `bundle info [gemname]` to see where a bundled gem is installed.
so to get the appropriate line we need to cut the output with head
and tail
as follows
1bundle --local | tail -n2 | head -n1
so the returned output will be similar to this
1Bundle complete! 27 Gemfile dependencies, 109 gems now installed.
now with ruby we can match the numbers in any string and extract them
1stats = `bundle --local | tail -n2 | head -n1`
2numbers = stats.scan(/[0-9]+/)
3direct_dep = numbers.first
4indirect_dep = numbers.last
so an ERB file as the following can print out these stats, I added some
sprinkles on top
1#!/usr/bin/env erb
2Project Name: <%= File.basename(Dir.pwd).capitalize %>
3<% stats = `bundle --local | tail -n2 | head -n1`.scan(/[0-9]+/) %>
4Direct Dependencies: <%= stats.first %>
5Indirect Dpendencies: <%= stats.last %>
6Direct Initializers: <%= Dir.glob('config/initializers/*.rb').count %>
7Initializers in Development Env: <%= `rake initializers`.lines.count %>
8Initializers in Production Env: <%= `RAILS_ENV=production rake initializers`.lines.count %>
9Controllers: <%= Dir.glob('app/controllers/**/*_controller.rb').count %>
10Models: <%= Dir.glob('app/models/**/*.*').reject{|f| f.include?('concern') }.count %>
11Views: <%= Dir.glob('app/views/**/*.*').count %>
12
13<%
14def files_for(ext)
15 Dir.glob('**/*.' + ext)
16end
17
18def size_for(ext)
19 files_for(ext).map{|f| File.size(f) }.inject(:+).to_i
20end
21%>
22Assets:
23JS: <%= files_for('js').count %> Files, <%= size_for('js') / 1024 %> KB
24SCSS: <%= files_for('scss').count %> Files, <%= size_for('scss') / 1024 %> KB
25PNG: <%= files_for('png').count %> Files, <%= size_for('png') / 1024 %> KB
26JPG: <%= files_for('jpg').count %> Files, <%= size_for('jpg') / 1024 %> KB
The final output will look like this
1#!/usr/bin/env erb
2Project Name: Web
3
4Direct Dependencies: 27
5Indirect Dependencies: 109
6Direct Initializers: 5
7Initializers in Development Env: 119
8Initializers in Production Env: 117
9Controllers: 10
10Models: 16
11Views: 65
12
13
14Assets:
15JS: 15 Files, 2569 KB
16SCSS: 30 Files, 86 KB
17PNG: 42 Files, 596 KB
18JPG: 0 Files, 0 KB
Numbers will be different for your project.
The following is my approach
dot
command to generate another format like pdf
orsvg
So lets try to have the same data visualized, first a simple graph, lets name it rails-graph
1#!/usr/bin/env erb
2digraph graphname {
3 a -> b -> c;
4 b -> d;
5}
executing this file with
1rails-graph | dot -Tpng > graph.png
You should see the following
Now lets put more nodes and numbers to this graph
1#!/usr/bin/env erb
2<%
3stats = `bundle --local | tail -n2 | head -n1`.scan(/[0-9]+/)
4ini = Dir.glob('config/initializers/*.rb').count
5prod_ini = `RAILS_ENV=production rake initializers`.lines.count
6dev_ini = `rake initializers`.lines.count
7dev_mwares = `rake middleware`.lines.count
8prod_mwares = `RAILS_ENV=production rake middleware`.lines.count
9
10m = Dir.glob('app/models/**/*.*').reject{|f| f.include?('concern') }.count
11v = Dir.glob('app/views/**/*.*').count
12c = Dir.glob('app/controllers/**/*_controller.rb').count
13routes = `rake routes`.lines.count - 1
14
15def files_for(ext)
16 Dir.glob('**/*.' + ext)
17end
18def count_for(ext)
19 files_for(ext).count
20end
21def size_for(ext)
22 files_for(ext).map{|f| File.size(f) }.inject(:+).to_i
23end
24%>
25digraph graphname {
26 direct_gems [label="Direct gems <%= stats.first %>"]
27 indirect_gems [label="Indirect gems <%= stats.last %>"]
28
29 initializers [label="<%= ini %> initializers"]
30 dev_initializers [label="<%= dev_ini %> Development initializers"]
31 prod_initializers [label="<%= prod_ini %> Production initializers"]
32
33 { rank=same; initializers dev_initializers prod_initializers }
34
35 dev_middlewares [label="<%= dev_mwares %> Development middlewares"]
36 prod_middlewares [label="<%= prod_mwares %> Production middlewares"]
37
38
39 controllers [label="Controllers: <%= c %>"]
40 models [label="Models: <%= m %>"]
41 views [label="Views: <%= v %>"]
42
43 routes [label="Routes <%= routes %>"]
44
45
46 indirect_gems -> direct_gems -> initializers -> routes
47 direct_gems -> dev_initializers -> dev_middlewares -> routes
48 direct_gems -> prod_initializers -> prod_middlewares -> routes
49
50 routes -> controllers
51
52 controllers -> models
53 controllers -> views
54
55 js [label="JS: <%= count_for('js') %> Files, <%= size_for('js') / 1024 %> KB"]
56 scss [label="SCSS: <%= count_for('scss') %> Files, <%= size_for('scss') / 1024 %> KB"]
57 png [label="PNG: <%= count_for('png') %> Files, <%= size_for('png') / 1024 %> KB"]
58 jpg [label="JPG: <%= count_for('jpg') %> Files, <%= size_for('jpg') / 1024 %> KB"]
59
60 views -> assets
61 assets -> js
62 assets -> scss
63 assets -> png
64 assets -> jpg
65
66}
The result will be as follows: